From 7572e8721c28235657038fad87e4c484ef5cc04b Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Mon, 13 Apr 2020 08:36:32 -0600 Subject: [PATCH] rework subrip format synchronization. (#536) * rework subrip format synchronization. support subsecond synchronization. validate option input. add debug messages for synchronization. correct option text for GUI. * update serialization reference files for corrected subrip help. --- reference/format3.txt | 6 +- reference/help.txt | 4 +- .../gpx_subsecond-sample-shifted~subrip.srt | 20 ++ .../track/gpx_subsecond-sample~subrip.srt | 20 +- subrip.cc | 191 +++++++++--------- testo.d/subrip.test | 9 +- 6 files changed, 144 insertions(+), 106 deletions(-) create mode 100644 reference/track/gpx_subsecond-sample-shifted~subrip.srt diff --git a/reference/format3.txt b/reference/format3.txt index 6aa10f669..ca3501c71 100644 --- a/reference/format3.txt +++ b/reference/format3.txt @@ -1272,11 +1272,11 @@ option skytraq-bin gps-week-rollover GPS week rollover period we're in (-1: best file ---w-- subrip srt SubRip subtitles for video mapping (.srt) subrip https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html -option subrip video_time Video position for which exact GPS time is known (hhmmss, default is 0:00:00) string https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_video_time +option subrip video_time Video position for which exact GPS time is known (hhmmss[.sss], default is 00:00:00,000) string https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_video_time -option subrip gps_time GPS time at position video_time (hhmmss, default is first timestamp of track) string https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_gps_time +option subrip gps_time GPS time at position video_time (hhmmss[.sss], default is first timestamp of track) string https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_gps_time -option subrip gps_date GPS date at position video_time (hhmmss, default is first timestamp of track) string https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_gps_date +option subrip gps_date GPS date at position video_time (yyyymmdd, default is first timestamp of track) string https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_gps_date option subrip format Format for subtitles string %s km/h %e m\n%t %l https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_format diff --git a/reference/help.txt b/reference/help.txt index 2429452d6..edd3bb9dd 100644 --- a/reference/help.txt +++ b/reference/help.txt @@ -641,8 +641,8 @@ File Types (-i and -o options): gps-week-rollover GPS week rollover period we're in (-1: best guess) subrip SubRip subtitles for video mapping (.srt) video_time Video position for which exact GPS time is known ( - gps_time GPS time at position video_time (hhmmss, default i - gps_date GPS date at position video_time (hhmmss, default i + gps_time GPS time at position video_time (hhmmss[.sss], def + gps_date GPS date at position video_time (yyyymmdd, default format Format for subtitles stmsdf Suunto Trek Manager (STM) .sdf files index Index of route (if more than one in source) diff --git a/reference/track/gpx_subsecond-sample-shifted~subrip.srt b/reference/track/gpx_subsecond-sample-shifted~subrip.srt new file mode 100644 index 000000000..9283eeb3d --- /dev/null +++ b/reference/track/gpx_subsecond-sample-shifted~subrip.srt @@ -0,0 +1,20 @@ +1 +00:00:00,000 --> 00:00:00,200 +50.6 km/h 289 m +17:47:25 Lat=49.79471 Lon=9.83400 + +2 +00:00:00,200 --> 00:00:00,400 +51.0 km/h 289 m +17:47:25 Lat=49.79473 Lon=9.83398 + +3 +00:00:00,400 --> 00:00:00,600 +50.7 km/h 289 m +17:47:25 Lat=49.79476 Lon=9.83395 + +4 +00:00:00,600 --> 00:00:00,800 +51.1 km/h 290 m +17:47:26 Lat=49.79478 Lon=9.83393 + diff --git a/reference/track/gpx_subsecond-sample~subrip.srt b/reference/track/gpx_subsecond-sample~subrip.srt index 00803a8ab..d74d7837a 100644 --- a/reference/track/gpx_subsecond-sample~subrip.srt +++ b/reference/track/gpx_subsecond-sample~subrip.srt @@ -1,25 +1,25 @@ 1 -00:00:00,200 --> 00:00:00,400 +00:00:00,000 --> 00:00:00,200 49.7 km/h 289 m 17:47:25 Lat=49.79469 Lon=9.83402 2 -00:00:00,400 --> 00:00:00,600 +00:00:00,200 --> 00:00:00,400 50.6 km/h 289 m -17:47:25 Lat=49.79472 Lon=9.83400 +17:47:25 Lat=49.79471 Lon=9.83400 3 -00:00:00,600 --> 00:00:00,800 +00:00:00,400 --> 00:00:00,600 51.0 km/h 289 m -17:47:25 Lat=49.79474 Lon=9.83398 +17:47:25 Lat=49.79473 Lon=9.83398 4 -00:00:00,800 --> 00:00:01,000 +00:00:00,600 --> 00:00:00,800 50.7 km/h 289 m -17:47:25 Lat=49.79476 Lon=9.83396 +17:47:25 Lat=49.79476 Lon=9.83395 5 -00:00:01,000 --> 00:00:02,000 -51.1 km/h 289 m -17:47:26 Lat=49.79479 Lon=9.83394 +00:00:00,800 --> 00:00:01,000 +51.1 km/h 290 m +17:47:26 Lat=49.79478 Lon=9.83393 diff --git a/subrip.cc b/subrip.cc index 9d2ea3c34..8db5d1568 100644 --- a/subrip.cc +++ b/subrip.cc @@ -19,9 +19,19 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include // for QDate +#include // for QDateTime, operator<< +#include // for QDebug +#include // for QString +#include // for QTime +#include // for QVector +#include // for UTC #include "defs.h" -#include /* for gmtime */ +#include "gbfile.h" // for gbfprintf, gbfclose, gbfopen, gbfwrite, gbfile +#include "src/core/datetime.h" // for DateTime +#include "src/core/logging.h" // for Fatal + #define MYNAME "subrip" @@ -29,7 +39,9 @@ static char* opt_videotime; static char* opt_gpstime; static char* opt_gpsdate; static char* opt_format; -static time_t time_offset; +static QDateTime gps_datetime; // Date time corresponding to video video_offset_ms +static QDateTime video_datetime; // Date time corresponding to video time 00:00:00,000. +static int video_offset_ms; static int stnum; static gbfile* fout; static const Waypoint* prevwpp; @@ -38,27 +50,16 @@ static double gradient; /* internal helper functions */ -static time_t -sync_time(time_t arg_gpstime, char* arg_videotime) +static QTime +video_time(const QDateTime& dt) { - static time_t videotime_t; - static struct tm* ptm_video; - static time_t result; - - videotime_t = 0; - ptm_video = gmtime(&videotime_t); - if (arg_videotime) { - sscanf(arg_videotime, "%2d%2d%2d", &ptm_video->tm_hour, &ptm_video->tm_min, &ptm_video->tm_sec); - } - videotime_t = mkgmtime(ptm_video); - result = (arg_gpstime - videotime_t); - return result; + return QTime::fromMSecsSinceStartOfDay(video_datetime.msecsTo(dt)); } static void subrip_prevwp_pr(const Waypoint* waypointp) { - QDateTime enddtime; + static long long deltaoffset; /* Now that we have the next waypoint, we can write out the subtitle for * the previous one. @@ -67,74 +68,80 @@ subrip_prevwp_pr(const Waypoint* waypointp) /* If this condition is not true, the waypoint is before the beginning of * the video and will be ignored */ - if (prevwpp->GetCreationTime().toTime_t() < time_offset) { + if (prevwpp->GetCreationTime() < video_datetime) { return; } gbfprintf(fout, "%d\n", stnum++); /* Writes start and end time for subtitle display to file. */ - QDateTime startdtime = prevwpp->GetCreationTime().addSecs(-time_offset); + QDateTime end_datetime; if (!waypointp) { - enddtime = startdtime.addSecs(1); + // prevwpp is the last waypoint, so we don't have a datetime for the + // next waypoint. Instead, estimate it from length of the previous + // video frame. + end_datetime = prevwpp->GetCreationTime().addMSecs(deltaoffset); } else { - enddtime = waypointp->GetCreationTime().addSecs(-time_offset); + end_datetime = waypointp->GetCreationTime(); + deltaoffset = prevwpp->GetCreationTime().msecsTo(waypointp->GetCreationTime()); } - QTime starttime = startdtime.toUTC().time(); - QTime endtime = enddtime.toUTC().time(); + QTime starttime = video_time(prevwpp->GetCreationTime()); + QTime endtime = video_time(end_datetime); gbfprintf(fout, "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d\n", - starttime.hour(), starttime.minute(), starttime.second(), starttime.msec(), - endtime.hour(), endtime.minute(), endtime.second(), endtime.msec()); + starttime.hour(), starttime.minute(), starttime.second(), starttime.msec(), + endtime.hour(), endtime.minute(), endtime.second(), endtime.msec()); for (char* c = opt_format; *c != '\0' ; c++) { char fmt; switch (*c) { case '%': - fmt = *++c; + fmt = *++c; is_fatal(fmt == '\0', "No character after %% in subrip format"); - + switch (fmt) { case 's': - if WAYPT_HAS(prevwpp, speed) - gbfprintf(fout, "%2.1f", MPS_TO_KPH(prevwpp->speed)); - else - gbfprintf(fout, "--.-"); + if WAYPT_HAS(prevwpp, speed) { + gbfprintf(fout, "%2.1f", MPS_TO_KPH(prevwpp->speed)); + } else { + gbfprintf(fout, "--.-"); + } break; case 'e': - if (prevwpp->altitude != unknown_alt) - gbfprintf(fout, "%4d", (int)prevwpp->altitude); - else + if (prevwpp->altitude != unknown_alt) { + gbfprintf(fout, "%4.0f", prevwpp->altitude); + } else { gbfprintf(fout, " -"); + } break; case 'v': gbfprintf(fout, "%2.2f", vspeed); break; case 'g': - gbfprintf(fout, "%2.1f%%", gradient); - break; - case 't': - { - QTime t = prevwpp->GetCreationTime().toUTC().time(); - gbfprintf(fout, "%02d:%02d:%02d", t.hour(), t.minute(), t.second()); - break; - } + gbfprintf(fout, "%2.1f%%", gradient); + break; + case 't': { + QTime t = prevwpp->GetCreationTime().toUTC().time(); + gbfprintf(fout, "%02d:%02d:%02d", t.hour(), t.minute(), t.second()); + break; + } case 'l': - // The +.00005 is for rounding. gbfprintf(fout, "Lat=%0.5lf Lon=%0.5lf", - prevwpp->latitude+.000005, prevwpp->longitude+.000005); + prevwpp->latitude, prevwpp->longitude); break; case 'c': - if (prevwpp->cadence != 0) + if (prevwpp->cadence != 0) { gbfprintf(fout, "%3u", prevwpp->cadence); - else + } else { gbfprintf(fout, " -"); + } break; case 'h': - if (prevwpp->heartrate != 0) + if (prevwpp->heartrate != 0) { gbfprintf(fout, "%3u", prevwpp->heartrate); - else + } else { gbfprintf(fout, " -"); + } break; } @@ -171,16 +178,22 @@ subrip_trkpt_pr(const Waypoint* waypointp) * but also pre-previous, so we calculate vspeed right before forgetting * the previous. */ - if ((stnum == 1) && (time_offset == 0)) - /* - * esoteric bug: GPS tracks created on Jan 1, 1970 at midnight would cause - * undesirable behavior here. But if you run into this problem, I assume - * you are capable of time-travel as well as inventing a high-tech system - * some 20 years before the rest of mankind does, so finding a prettier - * way of solving this should be trivial to you :-) - */ - { - time_offset = sync_time(waypointp->GetCreationTime().toTime_t(), opt_videotime); + if (!video_datetime.isValid()) { + if (!gps_datetime.isValid()) { + // If gps_date and gps_time options weren't used, then we use the + // datetime of the first waypoint to sync to the video. + gps_datetime = waypointp->GetCreationTime().toUTC(); + } + video_datetime = gps_datetime.addMSecs(-video_offset_ms).toUTC(); + if (global_opts.debug_level >= 2) { + qDebug().noquote() << "GPS track start is " + << waypointp->GetCreationTime().toUTC().toString(Qt::ISODateWithMs); + qDebug().noquote() << "Synchronizing" + << video_time(gps_datetime).toString("HH:mm:ss,zzz") + << "to" << gps_datetime.toString(Qt::ISODateWithMs); + qDebug().noquote() << "Video start 00:00:00,000 is" + << video_datetime.toString(Qt::ISODateWithMs); + } } if (prevwpp) { @@ -196,46 +209,45 @@ subrip_trkpt_pr(const Waypoint* waypointp) static void subrip_wr_init(const QString& fname) { - time_t gpstime_t; - stnum = 1; - - time_offset = 0; - prevwpp = nullptr; vspeed = 0; gradient = 0; + if ((opt_gpstime == nullptr) != (opt_gpsdate == nullptr)) { + Fatal() << MYNAME ": Either both or neither of the gps_date and gps_time options must be supplied!"; + } + gps_datetime = QDateTime(); if ((opt_gpstime != nullptr) && (opt_gpsdate != nullptr)) { - time(&gpstime_t); - struct tm* ptm_gps = gmtime(&gpstime_t); - if (opt_gpstime) { - sscanf(opt_gpstime, "%2d%2d%2d", &ptm_gps->tm_hour, &ptm_gps->tm_min, &ptm_gps->tm_sec); + QDate gps_date = QDate::fromString(opt_gpsdate, "yyyyMMdd"); + if (!gps_date.isValid()) { + Fatal().nospace() << MYNAME ": option gps_date value (" << opt_gpsdate << ") is invalid. Expected yyyymmdd."; } - if (opt_gpsdate) { - sscanf(opt_gpsdate, "%4d%2d%2d", &ptm_gps->tm_year, &ptm_gps->tm_mon, &ptm_gps->tm_mday); - /* - * Don't ask me why we need to do this nonsense, but it seems to be necessary: - * Years are two-digit since this was fashionable in the mid-1900s. - * For dates after 2000, just add 100 to the year. - * Months are zero-based (0 is January), but days are one-based. - * Makes sense, eh? - * Btw: correct dates will result in incorrect timestamps and you'll - * never figure out why. Suppose that's to confuse the Russians, - * given that the system was developed during the Cold War. But that - * is true for most of Unix. - * Make a difference - contribute to ReactOS. - */ - ptm_gps->tm_year-=1900; - ptm_gps->tm_mon--; + QTime gps_time = QTime::fromString(opt_gpstime, "HHmmss"); + if (!gps_time.isValid()) { + gps_time = QTime::fromString(opt_gpstime, "HHmmss.z"); + if (!gps_time.isValid()) { + Fatal().nospace() << MYNAME ": option gps_time value (" << opt_gpstime << ") is invalid. Expected hhmmss[.sss]"; + } } - gpstime_t = mkgmtime(ptm_gps); - time_offset = sync_time(gpstime_t, opt_videotime); + gps_datetime = QDateTime(gps_date, gps_time, Qt::UTC); + } + video_offset_ms = 0; + if (opt_videotime != nullptr) { + QTime video_time = QTime::fromString(opt_videotime, "HHmmss"); + if (!video_time.isValid()) { + video_time = QTime::fromString(opt_videotime, "HHmmss.z"); + if (!video_time.isValid()) { + Fatal().nospace() << MYNAME ": option video_time value (" << opt_videotime << ") is invalid. Expected hhmmss[.sss]."; + } + } + video_offset_ms = video_time.msecsSinceStartOfDay(); } - fout = gbfopen(fname, "wb", MYNAME); + video_datetime = QDateTime(); + fout = gbfopen(fname, "wb", MYNAME); } static void @@ -261,10 +273,9 @@ subrip_write() /* arguments: definitions of format-specific arguments */ static QVector subrip_args = { - // FIXME: document that gps_date and gps_time must be specified together or they will both be ignored and the timestamp of the first trackpoint will be used. - {"video_time", &opt_videotime, "Video position for which exact GPS time is known (hhmmss, default is 0:00:00)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr }, - {"gps_time", &opt_gpstime, "GPS time at position video_time (hhmmss, default is first timestamp of track)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr }, - {"gps_date", &opt_gpsdate, "GPS date at position video_time (hhmmss, default is first timestamp of track)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr }, + {"video_time", &opt_videotime, "Video position for which exact GPS time is known (hhmmss[.sss], default is 00:00:00,000)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr }, + {"gps_time", &opt_gpstime, "GPS time at position video_time (hhmmss[.sss], default is first timestamp of track)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr }, + {"gps_date", &opt_gpsdate, "GPS date at position video_time (yyyymmdd, default is first timestamp of track)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr }, {"format", &opt_format, "Format for subtitles", "%s km/h %e m\\n%t %l", ARGTYPE_STRING, ARG_NOMINMAX, nullptr }, }; diff --git a/testo.d/subrip.test b/testo.d/subrip.test index db19fc073..48e1bdc72 100644 --- a/testo.d/subrip.test +++ b/testo.d/subrip.test @@ -1,4 +1,11 @@ rm -f ${TMPDIR}/subrip.srt gpsbabel -i gpx -f ${REFERENCE}/track/gpx_subsecond-sample.gpx -o subrip -F ${TMPDIR}/subrip.srt -# FIXME: This can't work right until we move to "real" subsecond support. compare ${REFERENCE}/track/gpx_subsecond-sample~subrip.srt ${TMPDIR}/subrip.srt +gpsbabel -i gpx -f ${REFERENCE}/track/gpx_subsecond-sample.gpx -o subrip,gps_date=20110702,gps_time=174725.200 -F ${TMPDIR}/subrip.srt +compare ${REFERENCE}/track/gpx_subsecond-sample~subrip.srt ${TMPDIR}/subrip.srt +gpsbabel -i gpx -f ${REFERENCE}/track/gpx_subsecond-sample.gpx -o subrip,video_time=000000,gps_date=20110702,gps_time=174725.200 -F ${TMPDIR}/subrip.srt +compare ${REFERENCE}/track/gpx_subsecond-sample~subrip.srt ${TMPDIR}/subrip.srt +gpsbabel -i gpx -f ${REFERENCE}/track/gpx_subsecond-sample.gpx -o subrip,video_time=000000.200,gps_date=20110702,gps_time=174725.400 -F ${TMPDIR}/subrip.srt +compare ${REFERENCE}/track/gpx_subsecond-sample~subrip.srt ${TMPDIR}/subrip.srt +gpsbabel -i gpx -f ${REFERENCE}/track/gpx_subsecond-sample.gpx -o subrip,gps_date=20110702,gps_time=174725.400 -F ${TMPDIR}/subrip-shifted.srt +compare ${REFERENCE}/track/gpx_subsecond-sample-shifted~subrip.srt ${TMPDIR}/subrip-shifted.srt -- 2.30.2